Skip to content

VAPI-3165 Add Refer BXML verb support#118

Open
s-aher wants to merge 2 commits into
mainfrom
VAPI-3165
Open

VAPI-3165 Add Refer BXML verb support#118
s-aher wants to merge 2 commits into
mainfrom
VAPI-3165

Conversation

@s-aher

@s-aher s-aher commented May 25, 2026

Copy link
Copy Markdown

⚠️ DO NOT MERGE until api-specs#2142 is merged. The ticket warns "do not ship SDK before [spec PR] merge". Even merging this PR to main is unsafe in isolation: the next routine release cut from main would carry <Refer> to npm before the spec is finalized. Hold as Draft until VAPI-2916 / api-specs#2142 lands.

Summary

  • Adds the <Refer> BXML verb (models/bxml/verbs/Refer.ts) — hand-written, mirroring the <Transfer> pattern.
  • Exports Refer from models/bxml/verbs/index.ts.
  • Adds unit tests (tests/unit/models/bxml/verbs/Refer.test.ts).

Tracking: VAPI-3165 — parent epic VAPI-2459 — spec ticket VAPI-2916.

Critical semantic difference vs <Transfer>

<Transfer> keeps the call alive on success (Bandwidth bridges two legs).
<Refer> terminates the call on success — the remote SIP endpoint redirects away from Bandwidth entirely. This is a SIP protocol property, not a Bandwidth design choice.

The class docstring calls this out, and examples in this PR do not mimic Transfer's "play-after-success" pattern. Recovery BXML returned in response to referComplete is meaningful only for failure handling.

Scope decisions

Decision Choice
Spec sequencing BXML verb only this PR; ReferCompleteCallback model deferred to follow-up after spec merges and SDK regen runs.
<SipUri> child Single, required SipUri parameter (not Array) — matches spec ("exactly one").
Verb attributes referCompleteUrl, referCompleteMethod, tag only. No fallback URLs / auth / diversion (spec PR doesn't list them).
tag attribute Included — confirmed with Nitin Kumar on VAPI-2916.

Out of scope (follow-up PR)

After api-specs#2142 merges and the SDK regen job runs:

  1. Regenerate to produce models/refer-complete-callback.ts + auto-generated test.
  2. Hand-write scenario tests for the four cases the ticket calls out:
    • REFER rejected (e.g., 405): referCallStatus=failure, referSipResponseCode=405, no notifySipResponseCode
    • Destination unreachable: referCallStatus=failure, referSipResponseCode=202, notifySipResponseCode=<error>
    • NOTIFY timeout (30s): referCallStatus=failure, referSipResponseCode=202, no notifySipResponseCode
    • Success: referCallStatus=success

Test plan

  • npx jest tests/unit/models/bxml/verbs/Refer.test.ts — 3/3 pass
  • npx jest tests/unit/models/bxml/ — 56/56 pass (no regressions)
  • Smoke: new Response(new Refer({...}, sipUri)).toBxml() produces valid BXML
  • Hold until api-specs#2142 merges before flipping out of Draft

🤖 Generated with GitHub Copilot

@bwappsec

bwappsec commented May 25, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@s-aher s-aher marked this pull request as ready for review June 22, 2026 07:08
@s-aher s-aher requested review from a team as code owners June 22, 2026 07:08

@stampercasey stampercasey left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from Claude Code — see inline comments for individual findings. One blocker (regen job); the verb implementation is otherwise sound.

Regen job has not runmodels/refer-complete-callback.ts is absent and bandwidth.yml was not updated. The PR description lists this as a follow-up pending api-specs#2142, which has now merged (June 12). The regen needs to run and ReferCompleteCallback + the four scenario tests need to land before this is ready to merge.

* @param {ReferAttributes} attributes The attributes to add to the element
* @param {SipUri} sipUri The SipUri child element (required for a valid Refer)
*/
constructor(attributes?: ReferAttributes, sipUri?: SipUri) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] sipUri is optional but required by the spec

The spec mandates exactly one <SipUri> child. Making it optional here means TypeScript happily accepts new Refer({}) at compile time and produces invalid BXML at runtime.

Python makes sip_uri a required positional argument; C# throws at the builder call. Suggest flipping the signature to make sipUri required and swapping the parameter order to match Transfer's child-last convention:

constructor(sipUri: SipUri, attributes?: ReferAttributes) {
    super('Refer', undefined, attributes, [sipUri]);
}

If keeping optional for API flexibility, add a runtime guard:

if (!sipUri) throw new Error('Refer requires a SipUri child element');

@@ -0,0 +1,38 @@
import { NestableVerb } from '../NestableVerb';
import { SipUri } from './SipUri';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] SipUri carries Transfer-specific attributes

The imported SipUri accepts transferAnswerUrl, transferAnswerFallbackUrl, uui, username, password, etc. as optional attributes. Any of those passed here will serialize into the <SipUri> element inside <Refer>, producing malformed BXML — and TypeScript won't catch it.

Python resolved this with a dedicated ReferSipUri class accepting only uri. C# uses a nested Refer.SipUri with only a Uri property. A ReferSipUri type here (or at minimum a narrowed interface) would close that gap:

// models/bxml/verbs/ReferSipUri.ts
export class ReferSipUri extends Verb {
    constructor(uri: string) {
        super('SipUri', uri);
    }
}

* @param {SipUri} sipUri The SipUri to refer to
*/
setSipUri(sipUri: SipUri): void {
this.nestedVerbs = [sipUri];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] setSipUri replaces the entire nestedVerbs array

This works correctly today since <Refer> has exactly one child, but it silently drops any other nested verbs rather than updating a specific slot. Transfer's addTransferRecipients appends; this replaces. Worth a one-line comment making the intent explicit:

// Replaces the single required SipUri child — <Refer> allows exactly one.
this.nestedVerbs = [sipUri];

expect(xml).toContain('sip:bob@biloxi.example.com');
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] No test for Refer constructed without a SipUri

Since sipUri is currently optional, new Refer({}).toBxml() is valid TypeScript. A test documenting (and ideally asserting on) that behaviour would make the gap visible — and if sipUri is made required (see constructor comment), this becomes a compile-error test instead:

test('should throw when SipUri is missing', () => {
    expect(() => new Refer({})).toThrow();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants